// OhpmUploader.ts
import * as fs from 'fs';
import * as path from 'path';
import * as http from 'http';
import * as https from 'https';
import {HttpClient} from './HttpClient';

export class OhpmUploader {
    private client!: HttpClient;
    private baseOrigin!: string;   // e.g. http://127.0.0.1:8088
    private repoId?: string;

    async login(baseUrl: string, user: string, pass: string): Promise<boolean> {
        try {
            this.client = new HttpClient(baseUrl);
            const u = new URL(baseUrl);
            this.baseOrigin = `${u.protocol}//${u.hostname}${u.port ? ':' + u.port : ''}`;

            // 1️⃣ 登录，获取 x-csrf-token + Cookie
            const login = await this.client.postJSON('/api/auth/login', {name: user, password: pass});
            if (!login || !login.statusCode || login.statusCode >= 400) {
                console.error(`[login] ❌ 登录失败：HTTP ${login?.statusCode ?? '未知状态码'}`);
                return false;
            }

            const token = login.headers['x-csrf-token'] as string | undefined;
            if (!token) {
                console.error('[login] ❌ 登录失败：未获取到 x-csrf-token');
                return false;
            }

            this.client.token = token;
            console.log(`[login] ✅ 登录成功，token: ${token}`);

            // 2️⃣ 获取仓库 ID
            const list = await this.client.get<{ code: number; data: { id: string } }>('/api/repos');
            const repoId = list.json?.data?.id;
            if (!repoId) {
                console.error('[login] ❌ 登录失败：未能获取仓库 ID');
                return false;
            }

            this.repoId = repoId;
            console.log(`[login] ✅ 仓库 ID: ${repoId}`);

            return true; // ✅ 登录成功
        } catch (err: any) {
            console.error(`[login] ❌ 登录异常: ${err.message}`);
            return false;
        }
    }


    /** 单个 HAR —— 串行语义：await 调用即可 */
    async upHar(filePath: string): Promise<boolean> {
        if (!this.client) {
            console.error('请先调用 login()');
            return false;
        }
        if (!this.repoId) {
            console.error('repoId 缺失，请重新登录');
            return false;
        }

        const abs = path.resolve(filePath);
        if (!fs.existsSync(abs)) {
            console.error(`文件不存在: ${abs}`);
            return false;
        }

        const analyzed = await this.analyze(abs);
        if (!analyzed) {
            console.error(`[upHar] ❌ 分析阶段失败: ${filePath}`);
            return false;
        }

        const packageInfo = {
            name: analyzed.name,
            version: analyzed.version,
            author: analyzed.author ?? '',
            description: analyzed.description ?? '',
            license: analyzed.license ?? 'Apache-2.0',
            keywords: ''
        };

        console.log(`正在上传 [${packageInfo.name}] 模块, 路径：[${filePath}]`);
        return await this.upload(abs, packageInfo);
    }


    /** 批量串行上传 */
    async upHarBatch(files: string[]) {
        const successList: string[] = [];
        const failList: string[] = [];

        console.log(`\n======== 开始批量上传，共 ${files.length} 个文件 ========`);

        for (const f of files) {
            const ok = await this.upHar(f);
            if (ok) {
                successList.push(f);
            } else {
                failList.push(f);
            }
        }

        console.log(`\n======== 上传完成 ========`);
        console.log(`✅ 成功: ${successList.length}`);
        console.log(`❌ 失败: ${failList.length}`);

        if (successList.length > 0) {
            console.log(`\n✅ 上传成功的文件:`);
            successList.forEach((f, i) => console.log(`  ${i + 1}. ${f}`));
        }

        if (failList.length > 0) {
            console.log(`\n❌ 上传失败的文件:`);
            failList.forEach((f, i) => console.log(`  ${i + 1}. ${f}`));
        }

        console.log(`===========================`);

        // 如果你想让外部调用者也能拿到结果
        return {
            success: successList,
            failed: failList
        };
    }


    // ---------------- internal ----------------

    /** 从服务端获取新的 nonce */
    private async getNonce(): Promise<string> {
        const resp = await this.client.get<{ code: number; data: { nonce: string } }>('/api/security/nonce');
        const nonce = resp.json?.data?.nonce;
        if (!nonce) throw new Error('No nonce from /api/security/nonce');
        return nonce;
    }

    private async analyze(filePath: string): Promise<{ name: string; version: string; [k: string]: any } | false> {
        try {
            const boundary = '----WebKitFormBoundary' + Math.random().toString(16).slice(2);
            const fileName = path.basename(filePath);
            const prelude =
                `--${boundary}\r\n` +
                `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
                `Content-Type: application/octet-stream\r\n\r\n`;
            const epilogue = `\r\n--${boundary}--\r\n`;

            const stat = fs.statSync(filePath);
            const contentLength = Buffer.byteLength(prelude) + stat.size + Buffer.byteLength(epilogue);

            const nonce = await this.getNonce();
            const baseHeaders: Record<string, string | number> = {
                'Content-Type': `multipart/form-data; boundary=${boundary}`,
                'Content-Length': contentLength,
                'Accept': 'application/json, text/plain, */*',
                'Origin': this.baseOrigin,
                'Referer': `${this.baseOrigin}/`,
                'nonce': nonce,
                'nonce-time': String(Date.now()),
                'User-Agent': 'Mozilla/5.0 NodeUploader'
            };

            const options = this.requestOptions(`/api/repos/${this.repoId}/packages/analyze`, baseHeaders);
            const mod = this.isHttps() ? https : http;

            const body = await new Promise<string>((resolve, reject) => {
                const req = mod.request(options, (res) => {
                    let chunks = '';
                    res.setEncoding('utf8');
                    res.on('data', c => chunks += c);
                    res.on('end', () => {
                        if (!res.statusCode || res.statusCode >= 400) {
                            console.error(`[analyze] ❌ 失败: code=${res.statusCode}, body=${chunks}`);
                            return resolve(''); // 返回空串表示失败
                        }
                        resolve(chunks);
                    });
                });
                req.on('error', err => {
                    console.error(`[analyze] ❌ 网络错误: ${err.message}`);
                    resolve('');
                });
                req.write(prelude);
                fs.createReadStream(filePath)
                    .on('error', err => {
                        console.error(`[analyze] ❌ 文件读取失败: ${err.message}`);
                        resolve('');
                    })
                    .on('end', () => {
                        req.write(epilogue);
                        req.end();
                    })
                    .pipe(req, {end: false});
            });

            if (!body) return false;

            const parsed = JSON.parse(body);
            if (parsed?.code !== 200 || !parsed?.data) {
                console.error(`[analyze] ❌ 服务端返回异常: ${body}`);
                return false;
            }
            return parsed.data;
        } catch (err: any) {
            console.error(`[analyze] ❌ 异常: ${err.message}`);
            return false;
        }
    }


    private async upload(filePath: string, packageInfoObj: Record<string, any>): Promise<boolean> {
        const boundary = '----WebKitFormBoundary' + Math.random().toString(16).slice(2);
        const fileName = path.basename(filePath);
        const packageInfo = JSON.stringify(packageInfoObj);

        const prelude =
            `--${boundary}\r\n` +
            `Content-Disposition: form-data; name="packageInfo"\r\n\r\n` +
            `${packageInfo}\r\n` +
            `--${boundary}\r\n` +
            `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n` +
            `Content-Type: application/octet-stream\r\n\r\n`;
        const epilogue = `\r\n--${boundary}--\r\n`;

        const stat = fs.statSync(filePath);
        const contentLength = Buffer.byteLength(prelude) + stat.size + Buffer.byteLength(epilogue);

        // 每次上传都获取新的 nonce
        const nonce = await this.getNonce();
        const baseHeaders: Record<string, string | number> = {
            'Content-Type': `multipart/form-data; boundary=${boundary}`,
            'Content-Length': contentLength,
            'Accept': 'application/json, text/plain, */*',
            'Origin': this.baseOrigin,
            'Referer': `${this.baseOrigin}/`,
            'nonce': nonce,
            'nonce-time': String(Date.now()),
            'User-Agent': 'Mozilla/5.0 NodeUploader'
        };

        const options = this.requestOptions(`/api/repos/${this.repoId}/packages`, baseHeaders);
        const mod = this.isHttps() ? https : http;

        // ✅ 返回 Promise<boolean>
        return await new Promise<boolean>((resolve) => {
            const req = mod.request(options, (res) => {
                let chunks = '';
                res.setEncoding('utf8');
                res.on('data', (c) => (chunks += c));
                res.on('end', () => {
                    const success = res.statusCode && res.statusCode >= 200 && res.statusCode < 300;
                    if (!success) {
                        console.error(`[upload] ❌ 上传失败: code=${res.statusCode}, body=${chunks}`);
                        return resolve(false);
                    }
                    console.log(`[upload] ✅ 上传成功: ${fileName}`);
                    resolve(true);
                });
            });

            req.on('error', (err) => {
                console.error(`[upload] ❌ 网络错误: ${err.message}`);
                resolve(false);
            });

            req.write(prelude);
            const rs = fs.createReadStream(filePath);
            rs.on('error', (err) => {
                console.error(`[upload] ❌ 读取文件失败: ${err.message}`);
                resolve(false);
            });
            rs.on('end', () => {
                req.write(epilogue);
                req.end();
            });
            rs.pipe(req, {end: false});
        });
    }


    private requestOptions(pathname: string, headers: Record<string, string | number>) {
        const finalHeaders = (this.client as any).applyAuthHeaders
            ? (this.client as any).applyAuthHeaders(headers)
            : headers;

        const hostname = (this.client as any)['hostname'] as string;
        const port = (this.client as any)['port'] as number;

        return {hostname, port, path: pathname, method: 'POST', headers: finalHeaders};
    }


    private isHttps() {
        return ((this.client as any)['protocol'] as string) === 'https:';
    }

    /** 便捷静态方法：登录并批量上传（串行） */
    static async loginAndUploadBatch(
        baseUrl: string,
        user: string,
        pass: string,
        files: string[],
    ): Promise<{ success: string[]; failed: string[] }> {
        const uploader = new OhpmUploader();
        const ok = await uploader.login(baseUrl, user, pass);
        if (!ok) {
            return {success: [], failed: files.slice()};
        }
        return await uploader.upHarBatch(files);
    }

    /** 便捷静态方法：登录并上传单个 */
    static async loginAndUploadOne(
        baseUrl: string,
        user: string,
        pass: string,
        file: string,
    ): Promise<boolean> {
        const uploader = new OhpmUploader();
        const ok = await uploader.login(baseUrl, user, pass);
        if (!ok) return false;
        return await uploader.upHar(file);
    }

}
